    // Copyright (C) 2005  Davis E. King (davis@dlib.net)
    // License: Boost Software License   See LICENSE.txt for the full license.
    #ifndef DLIB_STATIC_MAP_KERNEl_1_
    #define DLIB_STATIC_MAP_KERNEl_1_

    #include "static_map_kernel_abstract.h"
    #include "../interfaces/map_pair.h"
    #include "../interfaces/enumerable.h"
    #include "../interfaces/remover.h"
    #include "../algs.h"
    #include "../serialize.h"
    #include <functional>

    namespace dlib
    {

        template <
            typename domain,
            typename range,
            typename compare = std::less<domain>
            >
        class static_map_kernel_1 : public enumerable<map_pair<domain,range> >
        {

            /*!
                INITIAL VALUE
                    - map_size == 0
                    - d == 0
                    - r == 0
                    - mp.d = 0;
                    - at_start_ == true

                CONVENTION
                    - size() == map_size
                    - if (size() > 0) then
                        - d == pointer to an array containing all the domain elements
                        - r == pointer to an array containing all the range elements
                        - for every i:  operator[](d[i]) == r[i]
                        - d is sorted according to operator<
                    - else
                        - d == 0
                        - r == 0

                    - current_element_valid() == (mp.d != 0)
                    - at_start() == (at_start_)
                    - if (current_element_valid()) then
                        - element() == mp
            !*/

            class mpair : public map_pair<domain,range>
            {
            public:
                const domain* d = nullptr;
                range* r = nullptr;

                const domain& key(
                ) const { return *d; }

                const range& value(
                ) const { return *r; }

                range& value(
                ) { return *r; }
            };

            // I would define this outside the class but Borland 5.5 has some problems
            // with non-inline templated friend functions.
            friend void deserialize (
                static_map_kernel_1& item,
                std::istream& in
            )
            {
                try
                {
                    item.clear();
                    unsigned long size;
                    deserialize(size,in);
                    item.map_size = size;
                    item.d = new domain[size];
                    item.r = new range[size];
                    for (unsigned long i = 0; i < size; ++i)
                    {
                        deserialize(item.d[i],in);
                        deserialize(item.r[i],in);
                    }
                }
                catch (serialization_error& e)
                {
                    item.map_size = 0;
                    if (item.d)
                    {
                        delete [] item.d;
                        item.d = 0;
                    }
                    if (item.r)
                    {
                        delete [] item.r;
                        item.r = 0;
                    }

                    throw serialization_error(e.info + "\n   while deserializing object of type static_map_kernel_1");
                }
                catch (...)
                {
                    item.map_size = 0;
                    if (item.d)
                    {
                        delete [] item.d;
                        item.d = 0;
                    }
                    if (item.r)
                    {
                        delete [] item.r;
                        item.r = 0;
                    }

                    throw;
                }
            }

            public:

                typedef domain domain_type;
                typedef range range_type;
                typedef compare compare_type;

                static_map_kernel_1(
                );

                virtual ~static_map_kernel_1(
                );

                void clear (
                );

                void load (
                    pair_remover<domain,range>& source
                );

                void load (
                    asc_pair_remover<domain,range,compare>& source
                );

                inline const range* operator[] (
                    const domain& d
                ) const;

                inline range* operator[] (
                    const domain& d
                );

                inline void swap (
                    static_map_kernel_1& item
                );

                // functions from the enumerable interface
                inline size_t size (
                ) const;

                inline bool at_start (
                ) const;

                inline void reset (
                ) const;

                inline bool current_element_valid (
                ) const;

                inline const map_pair<domain,range>& element (
                ) const;

                inline map_pair<domain,range>& element (
                );

                inline bool move_next (
                ) const;

            private:

                bool binary_search (
                    const domain& item,
                    unsigned long& pos
                ) const;
                /*!
                    ensures
                        - if (there is an item in d equivalent to item) then
                            - returns true
                            - d[#pos] is equivalent item
                        - else
                            - returns false
                !*/

                void sort_arrays (
                    unsigned long left,
                    unsigned long right
                );
                /*!
                    requires
                        - left and right are within the bounts of the array
                    ensures
                        - everything in the convention is still true and d[left] though
                          d[right] is sorted according to operator<
                !*/

                void qsort_partition (
                    unsigned long& partition_element,
                    const unsigned long left,
                    const unsigned long right
                );
                /*!
                    requires
                        - left < right
                        - left and right are within the bounts of the array
                    ensures
                        - the convention is still true
                        - left <= #partition_element <= right
                        - all elements in #d < #d[#partition_element] have
                          indices >= left and < #partition_element
                        - all elements in #d >= #d[#partition_element] have
                          indices >= #partition_element and <= right
                !*/

                unsigned long median (
                    unsigned long one,
                    unsigned long two,
                    unsigned long three
                );
                /*!
                    requires
                        - one, two, and three are valid indexes into d
                    ensures
                        - returns the median of d[one], d[two], and d[three]
                !*/

                // data members
                unsigned long map_size;
                domain* d;
                range* r;
                mutable mpair mp;
                mutable bool at_start_;
                compare comp;

                // restricted functions
                static_map_kernel_1(static_map_kernel_1&);        // copy constructor
                static_map_kernel_1& operator=(static_map_kernel_1&);    // assignment operator
        };

        template <
            typename domain,
            typename range,
            typename compare
            >
        inline void swap (
            static_map_kernel_1<domain,range,compare>& a,
            static_map_kernel_1<domain,range,compare>& b
        ) { a.swap(b); }

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
        // member function definitions
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename compare
            >
        static_map_kernel_1<domain,range,compare>::
        static_map_kernel_1(
        ) :
            map_size(0),
            d(0),
            r(0),
            at_start_(true)
        {
            mp.d = 0;
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename compare
            >
        static_map_kernel_1<domain,range,compare>::
        ~static_map_kernel_1(
        )
        {
            if (map_size > 0)
            {
                delete [] d;
                delete [] r;
            }
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename compare
            >
        void static_map_kernel_1<domain,range,compare>::
        clear (
        )
        {
            if (map_size > 0)
            {
                map_size = 0;
                delete [] d;
                delete [] r;
                d = 0;
                r = 0;
            }
            reset();
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename compare
            >
        void static_map_kernel_1<domain,range,compare>::
        load (
            pair_remover<domain,range>& source
        )
        {
            if (source.size() > 0)
            {
                domain* old_d = d;
                d = new domain[source.size()];
                try { r = new range[source.size()]; }
                catch (...) { delete [] d; d = old_d; throw; }

                map_size = source.size();

                for (unsigned long i = 0; source.size() > 0; ++i)
                    source.remove_any(d[i],r[i]);

                sort_arrays(0,map_size-1);
            }
            else
            {
                clear();
            }
            reset();
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename compare
            >
        void static_map_kernel_1<domain,range,compare>::
        load (
            asc_pair_remover<domain,range,compare>& source
        )
        {
            if (source.size() > 0)
            {
                domain* old_d = d;
                d = new domain[source.size()];
                try { r = new range[source.size()]; }
                catch (...) { delete [] d; d = old_d; throw; }

                map_size = source.size();

                for (unsigned long i = 0; source.size() > 0; ++i)
                    source.remove_any(d[i],r[i]);
            }
            else
            {
                clear();
            }
            reset();
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename compare
            >
        const range* static_map_kernel_1<domain,range,compare>::
        operator[] (
            const domain& d_item
        ) const
        {
            unsigned long pos;
            if (binary_search(d_item,pos))
                return r+pos;
            else
                return 0;
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename compare
            >
        range* static_map_kernel_1<domain,range,compare>::
        operator[] (
            const domain& d_item
        )
        {
            unsigned long pos;
            if (binary_search(d_item,pos))
                return r+pos;
            else
                return 0;
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename compare
            >
        size_t static_map_kernel_1<domain,range,compare>::
        size (
        ) const
        {
            return map_size;
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename compare
            >
        void static_map_kernel_1<domain,range,compare>::
        swap (
            static_map_kernel_1<domain,range,compare>& item
        )
        {
            exchange(map_size,item.map_size);
            exchange(d,item.d);
            exchange(r,item.r);
            exchange(mp,item.mp);
            exchange(at_start_,item.at_start_);
            exchange(comp,item.comp);
        }

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
        // enumerable function definitions
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename compare
            >
        bool static_map_kernel_1<domain,range,compare>::
        at_start (
        ) const
        {
            return (at_start_);
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename compare
            >
        void static_map_kernel_1<domain,range,compare>::
        reset (
        ) const
        {
            mp.d = 0;
            at_start_ = true;
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename compare
            >
        bool static_map_kernel_1<domain,range,compare>::
        current_element_valid (
        ) const
        {
            return (mp.d != 0);
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename compare
            >
        const map_pair<domain,range>& static_map_kernel_1<domain,range,compare>::
        element (
        ) const
        {
            return mp;
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename compare
            >
        map_pair<domain,range>& static_map_kernel_1<domain,range,compare>::
        element (
        )
        {
            return mp;
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename compare
            >
        bool static_map_kernel_1<domain,range,compare>::
        move_next (
        ) const
        {
            // if at_start() && size() > 0
            if (at_start_ && map_size > 0)
            {
                at_start_ = false;
                mp.r = r;
                mp.d = d;
                return true;
            }
            // else if current_element_valid()
            else if (mp.d != 0)
            {
                ++mp.d;
                ++mp.r;
                if (static_cast<unsigned long>(mp.d - d) < map_size)
                {
                    return true;
                }
                else
                {
                    mp.d = 0;
                    return false;
                }
            }
            else
            {
                at_start_ = false;
                return false;
            }
        }

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
        // private member function definitions
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename compare
            >
        bool static_map_kernel_1<domain,range,compare>::
        binary_search (
            const domain& item,
            unsigned long& pos
        ) const
        {
            unsigned long high = map_size;
            unsigned long low = 0;
            unsigned long p = map_size;
            unsigned long idx;
            while (p > 0)
            {
                p = (high-low)>>1;
                idx = p+low;
                if (comp(item , d[idx]))
                {
                    high = idx;
                }
                else if (comp(d[idx] , item))
                {
                    low = idx;
                }
                else
                {
                    pos = idx;
                    return true;
                }
            }
            return false;
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename compare
            >
        void static_map_kernel_1<domain,range,compare>::
        sort_arrays (
            unsigned long left,
            unsigned long right
        )
        {
            if ( left < right)
            {
                unsigned long partition_element;
                qsort_partition(partition_element,left,right);

                if (partition_element > 0)
                    sort_arrays(left,partition_element-1);
                sort_arrays(partition_element+1,right);
            }
        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename compare
            >
        void static_map_kernel_1<domain,range,compare>::
        qsort_partition (
            unsigned long& partition_element,
            const unsigned long left,
            const unsigned long right
        )
        {
            partition_element = right;

            unsigned long med = median(partition_element,left,((right-left)>>1) +left);
            exchange(d[partition_element],d[med]);
            exchange(r[partition_element],r[med]);

            unsigned long right_scan = right-1;
            unsigned long left_scan = left;

            while (true)
            {
                // find an element to the left of partition_element that needs to be moved
                while ( comp( d[left_scan] , d[partition_element]) )
                {
                    ++left_scan;
                }

                // find an element to the right of partition_element that needs to be moved
                while (
                    !(comp (d[right_scan] , d[partition_element])) &&
                    (right_scan > left_scan)
                )
                {
                    --right_scan;
                }
                if (left_scan >= right_scan)
                    break;

                exchange(d[left_scan],d[right_scan]);
                exchange(r[left_scan],r[right_scan]);

            }
            exchange(d[left_scan],d[partition_element]);
            exchange(r[left_scan],r[partition_element]);
            partition_element = left_scan;

        }

    // ----------------------------------------------------------------------------------------

        template <
            typename domain,
            typename range,
            typename compare
            >
        unsigned long static_map_kernel_1<domain,range,compare>::
        median (
            unsigned long one,
            unsigned long two,
            unsigned long three
        )
        {
            if ( comp( d[one] , d[two]) )
            {
                // one < two
                if ( comp( d[two] , d[three]) )
                {
                    // one < two < three : two
                    return two;
                }
                else
                {
                    // one < two >= three
                    if (comp( d[one] , d[three]))
                    {
                        // three
                        return three;
                    }
                }

            }
            else
            {
                // one >= two
                if ( comp(d[three] , d[one] ))
                {
                    // three <= one >= two
                    if ( comp(d[three] , d[two]) )
                    {
                        // two
                        return two;
                    }
                    else
                    {
                        // three
                        return three;
                    }
                }
            }
            return one;
        }

    // ----------------------------------------------------------------------------------------

    }

    #endif // DLIB_STATIC_MAP_KERNEl_1_